extends Node2D
class_name BattleField

@onready var battleSelectCellParent = $BattleSelectCellParent
@onready var battleGroundParent = $BattleGroundParent
@onready var cam:Camera2D = $Camera2D
@onready var enemyRoot = $EnemyRoot
@onready var heroRoot = $HeroRoot
@onready var stateMachine = $StateMachine
@onready var walkableHighlightRoot = $WalkableHighlightRoot
@onready var pathHighlightRoot = $PathHighlightRoot
@onready var targetHighlight = $TargetHighlight
@onready var commandBox = $BattleEntityCommandBox
@onready var battleBg = $CanvasLayerBg/TextureRectBg
@onready var roundTitle = $UI/RoundTitle

@export var scrollSpeedX = 300
@export var scrollSpeedY = 300
@export var scrollMinX = 5
@export var scrollMaxX = 315
@export var scrollMinY = 5
@export var scrollMaxY = 170
@export var entityMoveSpeed = 20

var totalWidth
var totalHeight
var cellSize = 16
var scrollHorizontal = 0
var scrollVertical = 0

var battleData
var cellCountHorizontal = 5
var cellCountVertical = 5
var heroDataArr:Array
var enemyDataArr:Array
var heroInstArr:Array
var enemyInstArr:Array
var currentSelectedHeroInst
var currentMoveableCells
var lastMouseHoverMoveableCell
var currentSelectEnemyInst


func _ready():
	add_to_group(Consts.GROUP_BATTLEFIELD)
	
	battleData = BattleData
	cellCountHorizontal = battleData.cellCountHorizontal
	cellCountVertical = battleData.cellCountVertical
	heroDataArr = battleData.heroDataArr
	enemyDataArr = battleData.enemyDataArr
	commandBox.battleField = self
	
	init()
	

func _exit_tree():
	remove_from_group(Consts.GROUP_BATTLEFIELD)


func init():
	initBg()
	initBattleField()
	initCam()
	initBattleEntity()
	startBattle()
	

func initBg():
	if GlobalData.enterBattleBgImage:
		battleBg.texture = Global.createTextureFromImage(GlobalData.enterBattleBgImage)


func startBattle():
	stateMachine.addState(Consts.battleFieldStateRoundTransition)
	

func initBattleEntity():
	for hero in heroDataArr:
		var battleEntityInst = Global.createSceneInst(Consts.RESOURCE_BATTLE_ENTITY)
		heroRoot.add_child(battleEntityInst)
		battleEntityInst.init(hero)
		heroInstArr.append(battleEntityInst)
		
	for enemy in enemyDataArr:
		var battleEntityInst = Global.createSceneInst(Consts.RESOURCE_BATTLE_ENTITY)
		enemyRoot.add_child(battleEntityInst)
		battleEntityInst.init(enemy)	
		enemyInstArr.append(battleEntityInst)
	
	
func initCam():
	var viewportSize = Global.viewportSize
	var targetCamPos = Vector2.ZERO
	if viewportSize.x > totalWidth:
		targetCamPos.x = -(viewportSize.x - totalWidth) / 2.0
	else:
		targetCamPos.x = 0
		
	if viewportSize.y > totalHeight:
		targetCamPos.y = -(viewportSize.y - totalHeight) / 2.0
	else:
		targetCamPos.y = 0
	Global.setCamPosition(cam, targetCamPos)
	

func initBattleField():
	totalWidth = cellSize * cellCountHorizontal
	totalHeight = cellSize * cellCountVertical
	
	battleGroundParent.scale = Vector2(totalWidth, totalHeight)

	
func _unhandled_input(event):
	if event is InputEventMouseButton:
		if event.pressed:
			pass
		else:
			pass
	elif event is InputEventMouseMotion:
		pass
		
	
func _physics_process(delta):
	stateMachine.run(delta)
	

func removeWalkableHighlight():
	for index in range(walkableHighlightRoot.get_child_count()):
		var childNode:Node2D = walkableHighlightRoot.get_child(index)
		childNode.queue_free()
	currentMoveableCells = null
		

func drawWalkableHighlight(centerCellPos:Vector2, move):
	var validHighlightCellArr:Array = bfsFindMoveableCells(centerCellPos, move, BattleData.enemiesCellPosArr())
	currentMoveableCells = validHighlightCellArr
	
	for cell in validHighlightCellArr:
		var inst:Node2D = Global.createSceneInst(Consts.RESOURCE_BATTLE_WALKABLEHIGHLIGHT)
		walkableHighlightRoot.add_child(inst)
		inst.position = cell2PosLeftUp(cell)
		

func drawPathHighlight(startPos, endPos, maxMove):
	var pathCells = findPath(startPos, endPos, maxMove, BattleData.enemiesCellPosArr())
	
	removePathHighlight()
	
	for cell in pathCells:
		var inst:Node2D = Global.createSceneInst(Consts.RESOURCE_BATTLE_PATHHIGHLIGHT)
		pathHighlightRoot.add_child(inst)
		inst.position = cell2PosLeftUp(cell)
		

func removePathHighlight():
	for index in range(pathHighlightRoot.get_child_count()):
		var childNode = pathHighlightRoot.get_child(index)
		childNode.queue_free()
		

func bfsFindMoveableCells(cellPos, move, obstacles:Array):
	var result = []
	var directions = [Vector2(1, 0), Vector2(-1, 0), Vector2(0, 1), Vector2(0, -1)]
	var visited = {}
	var queue = []
	
	queue.append([cellPos, 0])
	visited[cellPos] = true
	
	while not queue.is_empty():
		var current = queue.pop_front()
		var currentPos = current[0]
		var currentDistance = current[1]
		
		if currentDistance > move:
			continue
		
		result.append(currentPos)
		
		for direction in directions:
			var nextPos = currentPos + direction
			
			var hasObstacle = false
			for obstacle in obstacles:
				if obstacle.x == nextPos.x and obstacle.y == nextPos.y:
					hasObstacle = true
					break
					
			var withinBounds = isWithinBounds(nextPos)
			
			if withinBounds and not hasObstacle and not visited.has(nextPos):
				queue.append([nextPos, currentDistance + 1])
				visited[nextPos] = true
				
	return result


func isWithinBounds(cellPos):
	return cellPos.x >= 0 and cellPos.x < cellCountHorizontal and cellPos.y >= 0 and cellPos.y < cellCountVertical


func tryFindMoveableCellByCellPos(cellPos):
	var result = null
	if currentMoveableCells == null: return result
	
	for cell in currentMoveableCells:
		if cell.x == cellPos.x and cell.y == cellPos.y:
			result = cell
	return result
	

func calcCellDistance(cellPosFrom, cellPosTo):
	return abs(cellPosFrom.x - cellPosTo.x) + abs(cellPosFrom.y - cellPosTo.y)
	
	
func cell2PosLeftUp(cell:Vector2):
	return Vector2(cell.x * cellSize, cell.y * cellSize)


func cell2PosCenter(cell:Vector2):
	return Vector2(cell.x * cellSize + cellSize / 2.0, cell.y * cellSize + cellSize / 2.0)


func findHeroByCellPos(cellPos:Vector2):
	var result = null
	for hero in heroInstArr:
		if hero.data.battlePos.x == cellPos.x and hero.data.battlePos.y == cellPos.y:
			result = hero
	return result
	

func findEnemyByCellPos(cellPos:Vector2):
	var result = null
	for enemy in enemyInstArr:
		if enemy.data.battlePos.x == cellPos.x and enemy.data.battlePos.y == cellPos.y:
			result = enemy
	return result
	
	
func trySelectHero():
	var result = null
	var selectCell = getMousePositionCell()
	for hero:BattleEntity in heroInstArr:
		if hero.data.battlePos.x == selectCell.x and hero.data.battlePos.y == selectCell.y and not hero.data.battleActionFinish:
			result = hero
	return result
	

func deselectHero():
	if currentSelectedHeroInst != null:
		currentSelectedHeroInst.processDeselect()
		currentSelectedHeroInst = null
	
	
func getMousePositionCell():
	var mousePosition = Global.getMousePosition()
	var camPosition = Global.getCamPosition(cam)
	var cellX = floor((camPosition.x + mousePosition.x) / cellSize) 
	var cellY = floor((camPosition.y + mousePosition.y) / cellSize)
	return Vector2(cellX, cellY)


func processSelectCell(delta):
	showSelectCell()
	
	var mousePosition = Global.getMousePosition()
	var camPosition = Global.getCamPosition(cam)
	
	var cellX = floor((camPosition.x + mousePosition.x) / cellSize) 
	if cellX < 0: cellX = 0
	if cellX >= cellCountHorizontal: cellX = cellCountHorizontal - 1
	
	var cellY = floor((camPosition.y + mousePosition.y) / cellSize)
	if cellY < 0: cellY = 0
	if cellY >= cellCountVertical: cellY = cellCountVertical - 1
	
	battleSelectCellParent.position = Vector2(cellX * cellSize, cellY * cellSize)	


func processCamMove(delta):
	var mousePosition = Global.getMousePosition()
	
	if mousePosition.x <= scrollMinX:
		scrollHorizontal = -1
	elif mousePosition.x >= scrollMaxX:
		scrollHorizontal = 1
	else:
		scrollHorizontal = 0
	
	if mousePosition.y <= scrollMinY:
		scrollVertical = -1
	elif mousePosition.y >= scrollMaxY:
		scrollVertical = 1
	else:
		scrollVertical = 0
	
	var targetCamPos = Global.getCamPosition(cam)
	var viewportSize = Global.viewportSize
	if viewportSize.x <= totalWidth:
		targetCamPos.x = targetCamPos.x + scrollHorizontal * scrollSpeedX * delta
		if targetCamPos.x < 0: targetCamPos.x = 0
		if targetCamPos.x > (totalWidth - viewportSize.x):
			targetCamPos.x = (totalWidth - viewportSize.x)
	
	if viewportSize.y <= totalHeight:
		targetCamPos.y = targetCamPos.y + scrollVertical * scrollSpeedY * delta
		if targetCamPos.y < 0: targetCamPos.y = 0
		if targetCamPos.y > (totalHeight - viewportSize.y):
			targetCamPos.y = (totalHeight - viewportSize.y)
			
	Global.setCamPosition(cam, targetCamPos)
	
	
# delicated A*
#func findPath(startPos, endPos):
	#var queue = PriorityQueue.new()
	#var gScore = {}
	#var fScore = {}
	#var cameFrom = {}
	#
	#queue.push(startPos, 0)
	#gScore[startPos] = 0
	#fScore[startPos] = calcHeuristic(startPos, endPos)
	#
	#while not queue.empty():
		#var current = queue.pop()
		#
		#if current == endPos:
			#return reconstructPath(cameFrom, current)
			#
		#for direction in [Vector2(1, 0), Vector2(-1, 0), Vector2(0, 1), Vector2(0, -1)]:
			#var nextPos = current + direction
			#var tentativeGScore = gScore[current] + 1
			#
			#var hasEnemyEntity = findEnemyByCellPos(nextPos) != null
			#var withinBounds = isWithinBounds(nextPos)
			#
			#if not withinBounds or hasEnemyEntity: continue
			#
			#if not gScore.has(nextPos) or gScore[nextPos] > tentativeGScore:
				#cameFrom[nextPos] = current
				#gScore[nextPos] = tentativeGScore
				#fScore[nextPos] = gScore[nextPos] + calcHeuristic(nextPos, endPos)
				#
				#if not queue.has(nextPos):
					#queue.push(nextPos, fScore[nextPos])
					#
	#return []
	

func findPath(startPos, endPos, move, obstacles):
	var directions = [Vector2(1, 0), Vector2(-1, 0), Vector2(0, 1), Vector2(0, -1)]
	var gScore = {}
	var queue = []
	var cameFrom = {}
	
	queue.append(startPos)
	gScore[startPos] = 0
	
	while not queue.is_empty():
		var currentPos = queue.pop_front()
		var currentDistance = gScore[currentPos]
		
		for direction in directions:
			var nextPos = currentPos + direction
			
			var hasObstacle = false
			for obstacle in obstacles:
				if obstacle.x == nextPos.x and obstacle.y == nextPos.y:
					hasObstacle = true
					break
					
			var withinBounds = isWithinBounds(nextPos)
			var nextPosCost = currentDistance + 1
			
			if not withinBounds or hasObstacle or nextPosCost > move: continue
			
			if not gScore.has(nextPos) or nextPosCost < gScore[nextPos]:
				queue.append(nextPos)
				cameFrom[nextPos] = currentPos
				gScore[nextPos] = nextPosCost
	
	return reconstructPath(cameFrom, endPos)
			
		
func reconstructPath(cameFrom:Dictionary, current):
	var result = [current]
	while cameFrom.has(current):
		current = cameFrom[current]
		result.append(current)
	result.reverse()
	return result
	

func calcHeuristic(pos, goal):
	return abs(pos.x - goal.x) + abs(pos.y - goal.y)


class PriorityQueue:
	var elements = []
	
	func push(item, priority):
		elements.append({"item": item, "priority": priority})
		elements.sort_custom(sortPriority)
		pass
		
	
	func pop():
		return elements.pop_front()["item"]
		
	
	func empty():
		return len(elements) <= 0
	
	
	func has(item):
		for element in elements:
			if element["item"] == item:
				return true
		return false
	
	
	static func sortPriority(a, b):
		return int(a["priority"] - b["priority"]) 


func onHeroMoveArriveTarget():
	stateMachine.removeState(Consts.battleFieldStateHeroRoundHeroMove)
	stateMachine.addState(Consts.battleFieldStateHeroRoundAcceptCommand)
	
	
func hideSelectCell():
	battleSelectCellParent.hide()
	

func showSelectCell():
	battleSelectCellParent.show()
	
	
func openCommandBox():
	#simulate menuitems
	
	var menuItemLightAttack = CustomClasses.BattleEntityCommandBoxMenuItemData.new("轻攻击")
	var menuItemHeavyAttack = CustomClasses.BattleEntityCommandBoxMenuItemData.new("重攻击")
	var menuItemBattleSkill = CustomClasses.BattleEntityCommandBoxMenuItemData.new("战技")
	var menuItemMagic = CustomClasses.BattleEntityCommandBoxMenuItemData.new("魔法")
	var menuItemItem = CustomClasses.BattleEntityCommandBoxMenuItemData.new("道具")
	var menuItemWait = CustomClasses.BattleEntityCommandBoxMenuItemData.new("待命")
	var menuItemCancel = CustomClasses.BattleEntityCommandBoxMenuItemData.new("取消")
	
	var menuItemBattleSkillLion = CustomClasses.BattleEntityCommandBoxMenuItemData.new("狮子斩")
	var menuItemBattleSkillBloodMountain = CustomClasses.BattleEntityCommandBoxMenuItemData.new("尸山血海")
	
	var menuItemMagicCure = CustomClasses.BattleEntityCommandBoxMenuItemData.new("黄金树恢复")
	var menuItemMagicThunder = CustomClasses.BattleEntityCommandBoxMenuItemData.new("落雷")
	
	var menuItemItemFirePot = CustomClasses.BattleEntityCommandBoxMenuItemData.new("火焰壶")
	var menuItemItemThunderPot = CustomClasses.BattleEntityCommandBoxMenuItemData.new("雷电壶")
	
	menuItemBattleSkill.subMenuItemsData = [menuItemBattleSkillLion, menuItemBattleSkillBloodMountain]
	menuItemMagic.subMenuItemsData = [menuItemMagicCure, menuItemMagicThunder]
	menuItemItem.subMenuItemsData = [menuItemItemFirePot, menuItemItemThunderPot]
	
	var menuItemsData = [menuItemLightAttack, menuItemHeavyAttack, menuItemBattleSkill, menuItemMagic, menuItemItem, menuItemWait, menuItemCancel]
	
	commandBox.initAndShow(menuItemsData)
	

func cancelExecuteCommand():
	stateMachine.clearAndAddState(Consts.battleFieldStateHeroRoundAcceptCommand)


func finishExecuteCommand():
	if BattleData.isHeroRound:
		currentSelectedHeroInst.data.battleActionFinish = true
		currentSelectedHeroInst = null
		
		var result = BattleData.findHeroActionNotFinish()
		if result:
			stateMachine.clearAndAddState(Consts.battleFieldStateHeroRoundSelectHero)
		else:
			stateMachine.clearAndAddState(Consts.battleFieldStateRoundTransition)	
	else:
		currentSelectEnemyInst.data.battleActionFinish = true
		currentSelectEnemyInst = null
		
		var result = BattleData.findEnemyActionNotFinish()
		if result:
			stateMachine.clearAndAddState(Consts.battleFieldStateEnemyRoundEnemyMove)
		else:
			stateMachine.clearAndAddState(Consts.battleFieldStateRoundTransition)	
		

	
func onCancelCommandBox():
	currentSelectedHeroInst.cancelCommand()
	
	stateMachine.removeState(Consts.battleFieldStateHeroRoundAcceptCommand)
	stateMachine.addState(Consts.battleFieldStateHeroRoundReady2Move)
	

func onExecuteCommand(commandName, data):
	currentSelectedHeroInst.executeCommand(commandName, data)
	stateMachine.removeState(Consts.battleFieldStateHeroRoundAcceptCommand)
	stateMachine.addState(Consts.battleFieldStateHeroRoundExecuteCommand)
	

func drawTargetHighlight(cellPosArr:Array):
	for cell in cellPosArr:
		var inst:Node2D = Global.createSceneInst("BattlePathHighlight")
		targetHighlight.add_child(inst)
		inst.position = cell2PosLeftUp(cell)
		

func clearTargetHighlight():
	for index in range(targetHighlight.get_child_count()):
		var childNode = targetHighlight.get_child(index)
		childNode.queue_free()


func dirVec2Face(dirVec):
	if dirVec.x == -1: return "Left"
	if dirVec.x == 1: return "Right"
	if dirVec.y == -1: return "Up"
	if dirVec.y == 1: return "Down"
	
	
func showRoundTitle():
	roundTitle.showLabel(BattleData.isHeroRound)
	

func hideRoundTitle():
	roundTitle.hideAll()
	
	
func onRoundTransitionFinish():
	BattleData.resetEntityActionFinishState()
	
	if BattleData.isHeroRound:
		stateMachine.clearAndAddState(Consts.battleFieldStateHeroRoundSelectHero)
	else:
		stateMachine.clearAndAddState(Consts.battleFieldStateEnemyRoundEnemyMove)
		

func findEnemyInstByData(data):
	var result = null
	for enemyInst in enemyInstArr:
		if enemyInst.data == data:
			result = enemyInst
	return result
	

